package com.tbit.uqbike.service.business.tag.strategy.add;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import com.tbit.uqbike.enums.TagStatus;
import com.tbit.uqbike.enums.UserType;
import com.tbit.uqbike.object.business.UserInfo;
import com.tbit.uqbike.object.pojo.Machine;
import com.tbit.uqbike.object.pojo.tag.MachineTag;
import com.tbit.uqbike.object.pojo.vo.BatchOperationMsgVO;
import com.tbit.uqbike.service.base.MachineService;
import com.tbit.uqbike.service.business.tag.TagService;
import com.tbit.uqbike.service.business.tag.strategy.TagOperationStrategy;
import com.tbit.uqbike.webmanager.dao.core.tag.MachineTagDao;
import com.tbit.uqbike.webmanager.dto.TagBindDTO;
import com.tbit.uqbike.webmanager.resp.tag.TagResult;
import com.tbit.uqbike.webmanager.util.DbOperationUtil;
import com.tbit.uqbike.webmanager.util.collection.SetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 添加标签操作抽象类
 *
 *
 * @author <a href="mailto:chen.chaoxiong@tbit.com.cn">ChenChaoXiong</a>
 * @since 2024/7/12
 */
@Component
public abstract class AbstractAddTagOperation implements TagOperationStrategy {

    @Resource
    private MachineTagDao machineTagDao;

    @Autowired
    private TagService tagService;

    @Autowired
    protected MachineService machineService;

    @Override
    public BatchOperationMsgVO<TagResult> operate(TagBindDTO tagBindDTO) {
        return addTag(tagBindDTO);
    }


    protected abstract BatchOperationMsgVO<TagResult> addTag(TagBindDTO tagBindDTO);


    /**
     * 计算可添加的标签绑定记录
     * 添加标签绑定记录的标准流程，即给定machineIds和tagIds，计算出可添加的标签绑定记录并返回
     * 无论是通过machineNo还是userCode/accountId添加标签，都需要
     * 先转化为machineId，经过这个流程
     * @param machineIds
     * @param tagIds
     * @param brandId
     * @param accountId
     * @param userInfo
     * @return
     */
    protected List<MachineTag> caculateAddableMachineTags(Set<Integer> machineIds, Set<Long> tagIds, Integer brandId, Integer accountId, UserInfo userInfo,BatchOperationMsgVO<TagResult> result) {

        Pair<Set<Pair<Integer, Long>>,Set<Pair<Integer, Long>>> addableAndExistedTagBinds = fetchAddableAndExistedTagBinds(machineIds, tagIds);
        Set<Pair<Integer, Long>> addableTagBinds = addableAndExistedTagBinds.getKey();
        Set<Pair<Integer, Long>> existedTagBinds = addableAndExistedTagBinds.getValue();
        if (CollectionUtil.isNotEmpty(existedTagBinds)){
            result.putAllFailValue(
                    existedTagBinds.stream()
                                   .map(pair ->
                                           TagResult.builder()
                                                    .brandId(brandId)
                                                    .accountId(accountId)
                                                    .machineId(pair.getKey())
                                                    .tagId(pair.getValue().toString())
                                                    .success(false)
                                                    .message("Tag  bind record already exists")
                                                    .build()
                                   )
                                   .collect(Collectors.toList())
            );
        }

        return addableTagBinds.stream()
                              .map(pair -> buildNewMachineTags(pair, brandId, accountId, userInfo))
                              .collect(Collectors.toList());
    }

    /**
     * 可添加的和已存在的标签绑定记录
     *
     * @param machineIds
     * @param tagIds
     */
    private Pair<Set<Pair<Integer, Long>>,Set<Pair<Integer, Long>>> fetchAddableAndExistedTagBinds(Set<Integer> machineIds, Set<Long> tagIds) {
        // 所有可能的machineId-tagId组合
        Set<Pair<Integer, Long>> cartesianProduct = SetUtil.cartesianProduct(machineIds, tagIds);

        //已经存在的machineId-tagId组合
        Set<Pair<Integer, Long>> existTagBinds = machineTagDao.selectTagedListByMachineIdsAndTagIds(
                                                                      machineIds,
                                                                      tagIds
                                                              )
                                                              .stream()
                                                              .map(machineTag -> new Pair<>(machineTag.getMachineId(), machineTag.getTagId()))
                                                              .collect(Collectors.toSet());

        //不存在的machineId-tagId组合(可新增)
        Set<Pair<Integer, Long>> addAbleList = SetUtil.exclusiveElementsFromFirstSet(cartesianProduct, existTagBinds);
        return new Pair<>(addAbleList,existTagBinds);
    }


    /**
     * 校验标签是否存在并移除不存在的标签ID
     * 将不存在的标签ID放入异常结果集，并移除不存在的标签ID
     * 当移除不存在的标签ID后，校验集tagIds不为空则返回true
     *
     * @param tagIds
     * @param brandId
     * @param accountId
     * @param results
     * @return
     */
    protected boolean validateAndFilterTags(Set<Long> tagIds, Integer brandId, Integer accountId, BatchOperationMsgVO<TagResult> results) {

        Set<Long> tagIdsCopy = new HashSet<>(tagIds);

        //传递副本，避免修改原始集合
        Set<Long> notExistTagIds = tagService.extractEnableTagIds(tagIdsCopy);
        if (!notExistTagIds.isEmpty()) {
            results.putAllAbnormalValue(
                    notExistTagIds.stream()
                                  .map(tagId ->
                                          TagResult.builder()
                                                   .brandId(brandId)
                                                   .accountId(accountId)
                                                   .tagId(tagId.toString())
                                                   .success(false)
                                                   .message("Tag does not exist")
                                                   .build()
                                  )
                                  .collect(Collectors.toList())
            );
            tagIds.removeAll(notExistTagIds);
        }
        return CollectionUtil.isNotEmpty(tagIds);
    }


    protected boolean saveMachineTags(List<MachineTag> machineTagList,BatchOperationMsgVO<TagResult> results){
        //这里根据需要选择单个保存还是批量保存
        return saveSingle(machineTagList,results);
    }


    /**
     * 单个保存
     * 逐个保存，性能较低，但是可以获取到具体的插入失败的记录
     * @param machineTagList
     * @param results
     * @return
     */
    private boolean saveSingle(List<MachineTag> machineTagList,BatchOperationMsgVO<TagResult> results){
        AtomicBoolean isSuccess = new AtomicBoolean(false);
        machineTagList.forEach(machineTag -> {
            TagResult.TagResultBuilder builder = TagResult.builder()
                                                          .brandId(machineTag.getBrandId())
                                                          .accountId(machineTag.getAccountId())
                                                          .machineId(machineTag.getMachineId());
            try {
                if (DbOperationUtil.isSuccess(machineTagDao.insert(machineTag))){
                    builder.success(true);
                    builder.id(machineTag.getId().toString());
                    results.putSuccessValue(builder.build());
                    isSuccess.set(true);
                }else {
                    results.putFailValue(builder.success(false).message("Single Insert failed").build());
                }
            } catch (Exception e) {
                results.putAbnormalValue(builder.success(false).message("Single Insert exception").build());
            }
        });
        return isSuccess.get();
    }


    /**
     * 批量保存
     * 性能比单个保存高，但是无法获取到具体的插入失败的记录
     * 其中一个没有成功则返回false，调用方根据返回值判断是否回滚
     * @param machineTagList
     * @param results
     * @return
     */
    private boolean saveBatch(List<MachineTag> machineTagList,BatchOperationMsgVO<TagResult> results){
        AtomicBoolean isSuccess = new AtomicBoolean(false);
        try {
            if (DbOperationUtil.isSuccessBatch(machineTagDao.insertBatch(machineTagList),machineTagList.size())){
                machineTagList.forEach(machineTag -> {
                    TagResult.TagResultBuilder builder = TagResult.builder()
                                                                  .brandId(machineTag.getBrandId())
                                                                  .accountId(machineTag.getAccountId())
                                                                  .machineId(machineTag.getMachineId())
                                                                          .id(machineTag.getId().toString());
                    results.putSuccessValue(builder.success(true).build());
                });
                isSuccess.set(true);
            }else {
                machineTagList.forEach(machineTag -> {
                    TagResult.TagResultBuilder builder = TagResult.builder()
                                                                  .brandId(machineTag.getBrandId())
                                                                  .accountId(machineTag.getAccountId())
                                                                  .machineId(machineTag.getMachineId());
                    results.putFailValue(builder.success(false).message("Batch Insert failed").build());
                });
            }
        } catch (Exception e) {
            machineTagList.forEach(machineTag -> {
                TagResult machineTagResult = TagResult.builder()
                                                      .brandId(machineTag.getBrandId())
                                                      .accountId(machineTag.getAccountId())
                                                      .machineId(machineTag.getMachineId())
                                                      .success(false)
                                                      .message("Batch Insert exception")
                                                      .build();
                results.putAbnormalValue(machineTagResult);
            });
        }
        return isSuccess.get();
    }


    /**
     * 校验设备是否存在并移除不存在的设备ID
     * 将不存在的设备ID放入异常结果集,并移除不存在的设备ID
     * 当移除不存在的设备ID后，校验集machineIds不为空则返回true
     *
     * @param machineIds
     * @param brandId
     * @param accountId
     * @param results
     * @return
     */
    protected boolean validateAndFilterMachines(Set<Integer> machineIds, Integer brandId, Integer accountId, BatchOperationMsgVO<TagResult> results) {
        Set<Integer> machineIdsCopy = new HashSet<>(machineIds);
        Set<Integer> notExistsMachineIds = extractNonExistingMachineIds(machineIdsCopy);
        if (!notExistsMachineIds.isEmpty()) {
            results.putAllAbnormalValue(
                    notExistsMachineIds.stream()
                                       .map( machineId ->
                                               TagResult.builder()
                                                        .brandId(brandId)
                                                        .accountId(accountId)
                                                        .machineId(machineId)
                                                        .success(false)
                                                        .message("Machine does not exist")
                                                        .build()
                                       )
                                       .collect(Collectors.toList())
            );
            machineIds.removeAll(notExistsMachineIds);
        }
        return CollectionUtil.isNotEmpty(machineIds);
    }

    /**
     * 提取待校验集合 Set<Integer> machineIds 中不存在的设备ID
     *
     * @param machineIds
     * @return
     */
    private Set<Integer> extractNonExistingMachineIds(Set<Integer> machineIds) {
        List<Machine> existingMachines = machineService.getByIds(machineIds);
        Set<Integer> existingMachineIds = existingMachines.stream()
                                                          .map(Machine::getMachineId)
                                                          .collect(Collectors.toSet());
        return machineIds.stream()
                         .filter(id -> !existingMachineIds.contains(id))
                         .collect(Collectors.toSet());
    }



    private MachineTag buildNewMachineTags(Pair<Integer, Long> machineTagPair, Integer brandId, Integer accountId, UserInfo userInfo) {
        MachineTag machineTag = new MachineTag();
        machineTag.setBrandId(brandId);
        machineTag.setAccountId(accountId);
        machineTag.setMachineId(machineTagPair.getKey());
        machineTag.setTagId(machineTagPair.getValue());
        machineTag.setOperatorId(userInfo.getAccountUserId());
        machineTag.setOperatorPhone(userInfo.getPhone());
        machineTag.setBindStatus(TagStatus.ENABLED.getValue());
        machineTag.setUserType(UserType.MANAGER.getCode());
        machineTag.setCreateTime(LocalDateTime.now());
        machineTag.setUpdateTime(LocalDateTime.now());
        machineTag.setTagTime(LocalDateTime.now());
        return machineTag;

    }
}
